package com.estun.backroundManagementService.aop.operationLog;

import com.alibaba.fastjson.JSON;
import com.estun.backroundManagementService.service.LogInfoService;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.*;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import java.io.UnsupportedEncodingException;
import java.lang.reflect.Method;
import java.net.URLDecoder;
import java.nio.charset.StandardCharsets;
import java.time.LocalDateTime;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.stream.Collectors;

/**
 * 切面处理类，操作日志异常日志记录处理
 */
@Slf4j
@Aspect
@Component
public class LogAspect {

    private final LogInfoService logInfoService;
    /**
     * 统计请求的处理时间
     */
    ThreadLocal<Long> startTime = new ThreadLocal<>();

    public LogAspect(LogInfoService logInfoService) {
        this.logInfoService = logInfoService;
    }

    private static String getUsername() throws UnsupportedEncodingException {
        // 接收到请求，记录请求内容
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        if (attributes != null) {
            HttpServletRequest request = attributes.getRequest();
            String xToken = request.getHeader("X-Token");
            if (StringUtils.isBlank(xToken)) {
                log.info("token不存在");
                return "API";
            }
            //从token中获取用户名
            return URLDecoder.decode(xToken, StandardCharsets.UTF_8.toString());
            //后续验证待开发
        }
        log.warn("token错误");
        return "ERROR TOKEN";
    }

    /**
     * 设置操作日志切入点 记录操作日志 在注解的位置切入代码
     */
    @Pointcut("@annotation(Log)")
    public void logPoinCut() {
    }

    /**
     * 设置操作异常切入点记录异常日志 扫描所有controller包下操作
     */
    @Pointcut("execution(public * com.estun.backroundManagementService.modules.workOrder.controller..*.*(..))")
    public void exceptionLogPointCut() {
    }

    @Before("logPoinCut()")
    public void doBefore() {
        // 接收到请求，记录请求开始时间
        startTime.set(System.currentTimeMillis());
    }

    /**
     * 正常返回通知，拦截用户操作日志，连接点正常执行完成后执行， 如果连接点抛出异常，则不会执行
     */
    @AfterReturning(value = "logPoinCut()", returning = "keys")
    public void doAfterReturning(JoinPoint joinPoint, Object keys) {
        // 获取RequestAttributes
        RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();

        // 从获取RequestAttributes中获取HttpServletRequest的信息
        HttpServletRequest request = (HttpServletRequest) requestAttributes.resolveReference(RequestAttributes.REFERENCE_REQUEST);

        LogInfo logInfo = LogInfo.builder().build();
        try {
            // 从切面织入点处通过反射机制获取织入点处的方法
            MethodSignature signature = (MethodSignature) joinPoint.getSignature();

            // 获取切入点所在的方法
            Method method = signature.getMethod();

            // 获取请求的类名
            String className = joinPoint.getTarget().getClass().getName();

            // 获取操作
            Log log = method.getAnnotation(Log.class);
            if (Objects.nonNull(log)) {
                logInfo.setModule(log.module());
                logInfo.setType(log.type());
                logInfo.setMessage(log.desc());
            }
            logInfo.setMethod(className + "." + method.getName()); // 请求的方法名
            String collect = Arrays.stream(joinPoint.getArgs())
                    .filter(Objects::nonNull)
                    .map(Object::toString)
                    .collect(Collectors.joining(", "));
            String requestBodyStr = collect.length() > 400 ? collect.substring(0, 390) + "..." : collect;
            //JSON.toJSONString(convertMap(request.getParameterMap()))
            logInfo.setReqParam(requestBodyStr); // 请求参数
            String resParam = JSON.toJSONString(keys);
            //返回过场截取
            resParam = resParam.length() > 500 ? resParam.substring(0, 497) + "..." : resParam;
            logInfo.setResParam(resParam); // 返回结果
            logInfo.setUserId(getUsername()); // 请求用户ID
            logInfo.setUserName(getUsername()); // 请求用户名称
            logInfo.setIp(request.getRemoteAddr()); // 请求IP
            logInfo.setUri(request.getRequestURI()); // 请求URI
            logInfo.setCreateTime(LocalDateTime.now()); // 创建时间
            logInfo.setTakeUpTime(System.currentTimeMillis() - startTime.get()); // 耗时
            logInfoService.save(logInfo);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 异常返回通知，用于拦截异常日志信息 连接点抛出异常后执行
     */
    @AfterThrowing(pointcut = "exceptionLogPointCut()", throwing = "e")
    public void doAfterThrowing(JoinPoint joinPoint, Throwable e) {
        // 获取RequestAttributes
        RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();

        // 从获取RequestAttributes中获取HttpServletRequest的信息
        HttpServletRequest request = (HttpServletRequest) requestAttributes.resolveReference(RequestAttributes.REFERENCE_REQUEST);
        try {
            // 从切面织入点处通过反射机制获取织入点处的方法
            MethodSignature signature = (MethodSignature) joinPoint.getSignature();

            // 获取切入点所在的方法
            Method method = signature.getMethod();

            // 获取请求的类名
            String className = joinPoint.getTarget().getClass().getName();
            String requestBodyStr = Arrays.toString(joinPoint.getArgs()).substring(0, 400) + "...";
            logInfoService.saveErrorInfo(
                    LogErrorInfo.builder()
                            .reqParam(requestBodyStr) // 请求参数
                            .method(className + "." + method.getName()) // 请求方法名
                            .name(e.getClass().getName()) // 异常名称
                            .message(stackTraceToString(e.getClass().getName(), e.getMessage(), e.getStackTrace())) // 异常信息
                            .userId(getUsername()) // 操作员ID
                            .userName(getUsername()) // 操作员名称
                            .uri(request.getRequestURI()) // 操作URI
                            .ip(request.getRemoteAddr()) // 操作员IP
                            .createTime(LocalDateTime.now()) // 发生异常时间
                            .build()
            );
        } catch (Exception e2) {
            e2.printStackTrace();
        }
    }

    /**
     * 转换request 请求参数
     */
    public Map<String, String> convertMap(Map<String, String[]> paramMap) {
        Map<String, String> rtnMap = new HashMap<String, String>();
        for (String key : paramMap.keySet()) {
            rtnMap.put(key, paramMap.get(key)[0]);
        }
        return rtnMap;
    }

    /**
     * 转换异常信息为字符串
     */
    public String stackTraceToString(String exceptionName, String exceptionMessage, StackTraceElement[] elements) {
        StringBuilder strBuff = new StringBuilder();
        for (StackTraceElement stet : elements) {
            strBuff.append(stet).append("<br/>");
        }
        String message = exceptionName + ":" + exceptionMessage + "<br/>" + strBuff;
        return message.length() > 500 ? message.substring(0, 497) + "..." : message;
    }


}

